// type def
export const Uint8Array_ID = idof<Uint8Array>();
export const ArrayI32_ID = idof<Array<i32>>();
// function def
export function drawCanvasData(data:Uint8Array, imgWidth:i32, imgHeight:i32, scale:i32=1, color:Array<i32>=[0x00,0x00,0x00], backgroundColor:Array<i32>=[-1,-1,-1]): Uint8Array{
  let width:i32 = imgWidth/scale;
  let imgData = new Uint8Array(imgWidth*imgHeight*4);
  let cols:i32 = <i32>(width / 8);
  for (let y=0;y<imgHeight;y++){
    for (let x=0;x<imgWidth;x++){
      let bit:i32 = <i32>(x/scale) % 8;
      let pat:i32 = 0x80 >> bit;
      let col:i32 = <i32>(x/scale/8);
      let row:i32 = <i32>(y/scale);
      let pos:i32 = y * imgWidth + x;
      if ((data[row*cols + col] & pat) > 0){
        imgData[pos*4] = color[0]; //R
        imgData[pos*4+1] = color[1]; //G
        imgData[pos*4+2] = color[2]; //B
        imgData[pos*4+3] = 0xff; //A
      }else{
        if (backgroundColor[0] < 0){
          // default no background
          imgData[pos*4] = 0xff; //R
          imgData[pos*4+1] = 0xff; //G
          imgData[pos*4+2] = 0xff; //B
          imgData[pos*4+3] = 0x00; //A
        }else{
          // bgColor
          imgData[pos*4] = backgroundColor[0]; //R
          imgData[pos*4+1] = backgroundColor[1]; //G
          imgData[pos*4+2] = backgroundColor[2]; //B
          imgData[pos*4+3] = 0xff; //A
        }
      }
    }
  }
  return imgData
}

export function parseDataFromCanvas(imgData:Uint8Array, imgWidth:i32, imgHeight:i32, scale:i32=1, color:Array<i32>=[0x00,0x00,0x00]): Uint8Array{
  let width:i32 = imgWidth/scale;
  let height:i32 = imgHeight/scale;
  let dataSize:i32 = width * height / 8;
  let bytes = new Uint8Array(dataSize);
  let cols:i32 = width / 8;
  let rows:i32 = height;
  for (let row=0;row<rows;row++){
    for (let col=0;col<cols;col++){
      let byt:i32 = 0;
      for (let bit=0;bit<8;bit++){
        // parse byte
        let x:i32 = col*8 + bit;
        let pat:i32 = 0x80 >> bit;
        // get scaled pixel
        let pos:i32 = (row*scale)*width*scale + x*scale;
        if (imgData[pos*4] === color[0] && //R
            imgData[pos*4+1] === color[1] && //G
            imgData[pos*4+2] === color[2] && //B
            imgData[pos*4+3] === 0xff //A
          ){
          byt = byt | pat; //set bit to 1
        }
      }
      bytes[row*cols + col] = byt;
    }
  }
  return bytes;
}

export function convertToGrayScale(imgData:Uint8Array): Uint8Array {
  for (let i:i32=0; i<imgData.length; i+=4){
    let r:u8 = imgData[i];
    let g:u8 = imgData[i+1];
    let b:u8 = imgData[i+2];
    let gray:u8 = <u8>(r*0.299 + g*0.587 + b*0.114);
    imgData[i] = gray;
    imgData[i+1] = gray;
    imgData[i+2] = gray;
  }
  return imgData;
}

export function convertToBlackWhite(imgData:Uint8Array, grayThreshold:u8=127): Uint8Array {
  for (let i:i32=0; i<imgData.length; i+=4){
    let r:u8 = imgData[i];
    let g:u8 = imgData[i+1];
    let b:u8 = imgData[i+2];
    let gray:u8 = <u8>(r*0.299 + g*0.587 + b*0.114);
    if (gray >= grayThreshold){
      imgData[i] = 255;
      imgData[i+1] = 255;
      imgData[i+2] = 255;
    }else{
      imgData[i] = 0;
      imgData[i+1] = 0;
      imgData[i+2] = 0;
    }
  }
  return imgData;
}

const ditherMatrix4 = [
  [0, 8, 2, 10],
  [12, 4, 14, 6],
  [3, 11, 1, 9],
  [15, 7, 13, 5],
];
const ditherMatrix8 = [
  [0,48,12,60,3,51,15,63],
  [32,16,44,28,35,19,47,31],
  [8,56,4,52,11,59,7,55],
  [40,24,36,20,43,27,39,23],
  [2,50,14,62,1,49,13,61],
  [34,18,46,30,33,17,45,29],
  [10,58,6,54,9,57,5,53],
  [42,26,38,22,41,25,37,21]
];
const standerDitherMatrix = ditherMatrix4;
const standerDitherMatrixWidth = standerDitherMatrix.length;
const standerDitherMatrixHeight = standerDitherMatrix[0].length;
export function convertToBlackWhiteDither(imgData:Uint8Array, imgWidth:i32): Uint8Array {
  for (let i:i32=0; i<imgData.length; i+=4){
    let x:i32 = <i32>(i/4) % imgWidth;
    let y:i32 = <i32>(<i32>(i/4) / imgWidth);
    let r:u8 = imgData[i];
    let g:u8 = imgData[i+1];
    let b:u8 = imgData[i+2];
    let gray:u8 = <u8>(r*0.299 + g*0.587 + b*0.114);
    let threshold:u8 = <u8>standerDitherMatrix[y%standerDitherMatrixHeight][x%standerDitherMatrixWidth]; // threshold in standerDitherMatrix
    threshold = <u8>(256 * threshold / (standerDitherMatrixWidth*standerDitherMatrixHeight));
    if (gray >= threshold){
      imgData[i] = 255;
      imgData[i+1] = 255;
      imgData[i+2] = 255;
    }else{
      imgData[i] = 0;
      imgData[i+1] = 0;
      imgData[i+2] = 0;
    }
  }
  return imgData;
}

function spreadEffect(effectTable:Array<Array<i32>>, e:i32, x:i32, y:i32, w:i32, h:i32):void{
  /* -.- -x- 0.5
   * 0.0 0.5 0.0
   */
  if (x+1 < w){
    effectTable[y][x+1] = <i32>(e * 0.5);
  }
  if (y+1 < h){
    effectTable[y+1][x] = <i32>(e * 0.5);
  }
  if (y+1 < h && x+1 < w){
    effectTable[y+1][x+1] = <i32>(e * 0.0);
  }
  if (y+1 < h && x-1 >= 0){
    effectTable[y+1][x-1] = <i32>(e * 0.0);
  }
}
export function convertToBlackWhiteFloydSteinberg(imgData:Uint8Array, imgWidth:i32): Uint8Array {
  // init effect table
  let imgHeight:i32 = imgData.length / 4 / imgWidth;
  let effectTable:Array<Array<i32>> = new Array<Array<i32>>(imgHeight);
  for (let i:i32=0; i<effectTable.length; i++){
    effectTable[i] = new Array<i32>(imgWidth);
    effectTable[i].fill(0);
  }
  for (let i:i32=0; i<imgData.length; i+=4){
    // from top to bottom, from left to right
    let x:i32 = <i32>(i/4) % imgWidth;
    let y:i32 = <i32>(<i32>(i/4) / imgWidth);
    let r:u8 = imgData[i];
    let g:u8 = imgData[i+1];
    let b:u8 = imgData[i+2];
    let gray:i32 = <i32>(r*0.299 + g*0.587 + b*0.114) + effectTable[y][x];
    if (gray >= 127){
      // white
      imgData[i] = 255;
      imgData[i+1] = 255;
      imgData[i+2] = 255;
      let e:i32 = gray - 256;
      spreadEffect(effectTable, e, x, y, imgWidth, imgHeight);
    }else{
      // black
      imgData[i] = 0;
      imgData[i+1] = 0;
      imgData[i+2] = 0;
      let e:i32 = gray - 0;
      spreadEffect(effectTable, e, x, y, imgWidth, imgHeight);
    }
  }
  return imgData;
}
